/*
 * Description: process preload wnd show
 */
#include <Windows.h>
#include <set>
#include <fstream>
#include <iostream>
#include <vector>
#include <tlhelp32.h>
#include <shlwapi.h>
#include <string>
#pragma comment(lib, "shlwapi.lib")

namespace {
DWORD g_processId = 0;  // 预加载进程ID
std::string g_processName = "";  // 预加载进程Name
const std::string PNAME_AUTOCAD = "acad.exe";
const std::string PNAME_XMIND = "Xmind.exe";
const std::string ACAD_HIDE_WND_FLAG = "Autodesk AutoCAD 2024";
const std::string ACAD_FILE_WND_FLAG = "Drawing1.dwg";
const std::string XMIND_HIDE_WND_FLAG = "Xmind";
static const int ARGV_SHOW_IDX = 1;                // 需要执行的动作参数
static const int ARGV_SHOW_AND_OPEN_FILE_IDX = 2;  // 需要执行的动作的路径
static const int ARGV_SHOW_PROCESS_NAME_IDX = 3;   // 需要执行的进程名称
static const int ARGV_MAX_IDX = 4;
static const std::string ACTION_SHOW = "show";
static const std::string ACTION_SHOW_AND_REOPEN_FILE = "reopen";
static const std::string FILE_OPEN_ACTION = "open";

BOOL EnumChildProcDwgHide(HWND hwnd, LPARAM lParam)
{
    char szTitle[MAX_PATH] = {0};
    LPSTR lpWindowName = szTitle;
    LPSTR lpName = const_cast<char *>(ACAD_FILE_WND_FLAG.c_str());

    GetWindowTextA(hwnd, lpWindowName, MAX_PATH);
    if (!strcmp(lpWindowName, lpName)) {
        PostMessage(hwnd, WM_CLOSE, 0, 0);
    }

    return TRUE;
}

BOOL EnumWndProcAndShow(HWND hwnd, LPARAM lParam)
{
    if (g_processId == 0) {
        return FALSE;
    }
    DWORD processId;
    GetWindowThreadProcessId(hwnd, &processId);
    if (processId == g_processId) {
        char szTitle[MAX_PATH] = {0};
        char szClass[MAX_PATH] = {0};
        LPSTR lpClassName = szClass;
        LPSTR lpWindowName = szTitle;
        GetWindowTextA(hwnd, lpWindowName, MAX_PATH);
        GetClassNameA(hwnd, lpClassName, MAX_PATH);
        if (g_processName == PNAME_AUTOCAD) {
            EnumChildWindows(hwnd, ::EnumChildProcDwgHide, lParam);
        }
    }
    return TRUE;
}

void ProcessPreloadGetProcessID(const std::string &name)
{
    PROCESSENTRY32 pe32 = {0};
    pe32.dwSize = sizeof(PROCESSENTRY32);
    // 获取全部进程快照
    HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE || hProcessSnap == nullptr) {
        return;
    }

    // 获取快照中第一条信息
    BOOL bRet = ::Process32First(hProcessSnap, &pe32);
    while (bRet) {
        if (strcmp(pe32.szExeFile, name.c_str()) == 0) {
            // 显示 Process ID
            g_processId = pe32.th32ProcessID;
            g_processName = name;
            break;
        }
        // 获取快照中下一条信息
        bRet = ::Process32Next(hProcessSnap, &pe32);
    }
    // 关闭句柄
    if (hProcessSnap != INVALID_HANDLE_VALUE && hProcessSnap != nullptr) {
        ::CloseHandle(hProcessSnap);
    }
    hProcessSnap = INVALID_HANDLE_VALUE;
    return;
}

bool PreProcessFileExists(const std::string& filePath)
{
    WIN32_FILE_ATTRIBUTE_DATA attData = {0};
    bool ret = false;
    if (GetFileAttributesExA(filePath.c_str(), GetFileExInfoStandard, &attData) == TRUE) {
        ret = ((attData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0);
    }
    return ret;
}

bool PreProcessFilePatchCheck(const std::string &filePath)
{
    if (filePath.empty()) {
        return false;
    }
    if (!PreProcessFileExists(filePath)) {
        return false;
    }
    if ((filePath.find("..\\") != std::string::npos) || (filePath.find("../") != std::string::npos)) {
        return false;
    }
    int num = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, NULL, 0);
    if (num <= 0) {
        return false;
    }
    wchar_t *wide = new(std::nothrow) wchar_t[num];
    if (wide == nullptr) {
        return false;
    }
    MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, wide, num);
    if (PathIsNetworkPathW(wide)) {
        delete[] wide;
        return false;
    }
    delete[] wide;
    return true;
}

const auto showProcessProc = [](HWND hwnd, LPARAM lParam) -> BOOL {
    std::pair<std::string, std::string> *args = reinterpret_cast<std::pair<std::string, std::string> *>(lParam);
    if (args == nullptr) {
        return false;
    }
    char wndTextCStr[MAX_PATH] = { 0 };
    GetWindowText(hwnd, wndTextCStr, MAX_PATH);
    std::string wndText = wndTextCStr;
    if (wndText.find(ACAD_HIDE_WND_FLAG) != std::string::npos) {
        ShowWindow(hwnd, SW_SHOWMINNOACTIVE);
        ShowWindow(hwnd, SW_RESTORE);
        SetForegroundWindow(hwnd);
        if (args->first == ACTION_SHOW_AND_REOPEN_FILE) {
            ShellExecute(hwnd, FILE_OPEN_ACTION.c_str(), args->second.c_str(), nullptr, nullptr, SW_SHOW);
        }
        return false;
    }
    return true;
};

void ShowProcessMainWindow(std::string &action, std::string &filePath)
{
    std::string hideWindowFlag = "";
    if (g_processName == PNAME_AUTOCAD) {
        std::pair<std::string, std::string> args = { action, filePath };
        EnumWindows(showProcessProc, (LPARAM)&args);
    } else if (g_processName == PNAME_XMIND) {
        hideWindowFlag = XMIND_HIDE_WND_FLAG;
        HWND hWnd = FindWindowA(NULL, hideWindowFlag.c_str());
        if (hWnd) {
            if (action == ACTION_SHOW_AND_REOPEN_FILE) {
                LPCSTR strPath = filePath.c_str();
                LPCSTR fileAct = FILE_OPEN_ACTION.c_str();
                ShellExecuteA(hWnd, fileAct, strPath, NULL, NULL, SW_SHOW);
                return;
            }
            ShowWindow(hWnd, SW_MAXIMIZE);
            ShowWindow(hWnd, SW_SHOWNORMAL);
            SetForegroundWindow(hWnd);
        }
    }
}
}  // namespace
void main(int argc, char **argv)
{
    if (argc < ARGV_MAX_IDX) {
        return;
    }
    std::string argvAction = argv[ARGV_SHOW_IDX];
    std::string argvFilePath = argc > ARGV_SHOW_AND_OPEN_FILE_IDX ? argv[ARGV_SHOW_AND_OPEN_FILE_IDX] : "";
    std::string argvProcessName = argv[ARGV_SHOW_PROCESS_NAME_IDX];
    if (!PreProcessFilePatchCheck(argvFilePath)) {
        return;
    }
    ProcessPreloadGetProcessID(argvProcessName);
    ShowProcessMainWindow(argvAction, argvFilePath);
    EnumWindows(::EnumWndProcAndShow, 0);
    return;
}